/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

var zrUtil = require("static/plugins/js/zrender/lib/core/util");

var pathTool = require("static/plugins/js/zrender/lib/tool/path");

var colorTool = require("static/plugins/js/zrender/lib/tool/color");

var matrix = require("static/plugins/js/zrender/lib/core/matrix");

var vector = require("static/plugins/js/zrender/lib/core/vector");

var Path = require("static/plugins/js/zrender/lib/graphic/Path");

var Transformable = require("static/plugins/js/zrender/lib/mixin/Transformable");

var ZImage = require("static/plugins/js/zrender/lib/graphic/Image");

exports.Image = ZImage;

var Group = require("static/plugins/js/zrender/lib/container/Group");

exports.Group = Group;

var Text = require("static/plugins/js/zrender/lib/graphic/Text");

exports.Text = Text;

var Circle = require("static/plugins/js/zrender/lib/graphic/shape/Circle");

exports.Circle = Circle;

var Sector = require("static/plugins/js/zrender/lib/graphic/shape/Sector");

exports.Sector = Sector;

var Ring = require("static/plugins/js/zrender/lib/graphic/shape/Ring");

exports.Ring = Ring;

var Polygon = require("static/plugins/js/zrender/lib/graphic/shape/Polygon");

exports.Polygon = Polygon;

var Polyline = require("static/plugins/js/zrender/lib/graphic/shape/Polyline");

exports.Polyline = Polyline;

var Rect = require("static/plugins/js/zrender/lib/graphic/shape/Rect");

exports.Rect = Rect;

var Line = require("static/plugins/js/zrender/lib/graphic/shape/Line");

exports.Line = Line;

var BezierCurve = require("static/plugins/js/zrender/lib/graphic/shape/BezierCurve");

exports.BezierCurve = BezierCurve;

var Arc = require("static/plugins/js/zrender/lib/graphic/shape/Arc");

exports.Arc = Arc;

var CompoundPath = require("static/plugins/js/zrender/lib/graphic/CompoundPath");

exports.CompoundPath = CompoundPath;

var LinearGradient = require("static/plugins/js/zrender/lib/graphic/LinearGradient");

exports.LinearGradient = LinearGradient;

var RadialGradient = require("static/plugins/js/zrender/lib/graphic/RadialGradient");

exports.RadialGradient = RadialGradient;

var BoundingRect = require("static/plugins/js/zrender/lib/core/BoundingRect");

exports.BoundingRect = BoundingRect;

var IncrementalDisplayable = require("static/plugins/js/zrender/lib/graphic/IncrementalDisplayable");

exports.IncrementalDisplayable = IncrementalDisplayable;

var subPixelOptimizeUtil = require("static/plugins/js/zrender/lib/graphic/helper/subPixelOptimize");

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
var mathMax = Math.max;
var mathMin = Math.min;
var EMPTY_OBJ = {};
var Z2_EMPHASIS_LIFT = 1; // key: label model property nane, value: style property name.

var CACHED_LABEL_STYLE_PROPERTIES = {
    color: "textFill",
    textBorderColor: "textStroke",
    textBorderWidth: "textStrokeWidth",
};
var EMPHASIS = "emphasis";
var NORMAL = "normal"; // Reserve 0 as default.

var _highlightNextDigit = 1;
var _highlightKeyMap = {};
var _customShapeMap = {};
/**
 * Extend shape with parameters
 */

function extendShape(opts) {
    return Path.extend(opts);
}
/**
 * Extend path
 */

function extendPath(pathData, opts) {
    return pathTool.extendFromString(pathData, opts);
}
/**
 * Register a user defined shape.
 * The shape class can be fetched by `getShapeClass`
 * This method will overwrite the registered shapes, including
 * the registered built-in shapes, if using the same `name`.
 * The shape can be used in `custom series` and
 * `graphic component` by declaring `{type: name}`.
 *
 * @param {string} name
 * @param {Object} ShapeClass Can be generated by `extendShape`.
 */

function registerShape(name, ShapeClass) {
    _customShapeMap[name] = ShapeClass;
}
/**
 * Find shape class registered by `registerShape`. Usually used in
 * fetching user defined shape.
 *
 * [Caution]:
 * (1) This method **MUST NOT be used inside echarts !!!**, unless it is prepared
 * to use user registered shapes.
 * Because the built-in shape (see `getBuiltInShape`) will be registered by
 * `registerShape` by default. That enables users to get both built-in
 * shapes as well as the shapes belonging to themsleves. But users can overwrite
 * the built-in shapes by using names like 'circle', 'rect' via calling
 * `registerShape`. So the echarts inner featrues should not fetch shapes from here
 * in case that it is overwritten by users, except that some features, like
 * `custom series`, `graphic component`, do it deliberately.
 *
 * (2) In the features like `custom series`, `graphic component`, the user input
 * `{tpye: 'xxx'}` does not only specify shapes but also specify other graphic
 * elements like `'group'`, `'text'`, `'image'` or event `'path'`. Those names
 * are reserved names, that is, if some user register a shape named `'image'`,
 * the shape will not be used. If we intending to add some more reserved names
 * in feature, that might bring break changes (disable some existing user shape
 * names). But that case probably rearly happen. So we dont make more mechanism
 * to resolve this issue here.
 *
 * @param {string} name
 * @return {Object} The shape class. If not found, return nothing.
 */

function getShapeClass(name) {
    if (_customShapeMap.hasOwnProperty(name)) {
        return _customShapeMap[name];
    }
}
/**
 * Create a path element from path data string
 * @param {string} pathData
 * @param {Object} opts
 * @param {module:zrender/core/BoundingRect} rect
 * @param {string} [layout=cover] 'center' or 'cover'
 */

function makePath(pathData, opts, rect, layout) {
    var path = pathTool.createFromString(pathData, opts);

    if (rect) {
        if (layout === "center") {
            rect = centerGraphic(rect, path.getBoundingRect());
        }

        resizePath(path, rect);
    }

    return path;
}
/**
 * Create a image element from image url
 * @param {string} imageUrl image url
 * @param {Object} opts options
 * @param {module:zrender/core/BoundingRect} rect constrain rect
 * @param {string} [layout=cover] 'center' or 'cover'
 */

function makeImage(imageUrl, rect, layout) {
    var path = new ZImage({
        style: {
            image: imageUrl,
            x: rect.x,
            y: rect.y,
            width: rect.width,
            height: rect.height,
        },
        onload: function (img) {
            if (layout === "center") {
                var boundingRect = {
                    width: img.width,
                    height: img.height,
                };
                path.setStyle(centerGraphic(rect, boundingRect));
            }
        },
    });
    return path;
}
/**
 * Get position of centered element in bounding box.
 *
 * @param  {Object} rect         element local bounding box
 * @param  {Object} boundingRect constraint bounding box
 * @return {Object} element position containing x, y, width, and height
 */

function centerGraphic(rect, boundingRect) {
    // Set rect to center, keep width / height ratio.
    var aspect = boundingRect.width / boundingRect.height;
    var width = rect.height * aspect;
    var height;

    if (width <= rect.width) {
        height = rect.height;
    } else {
        width = rect.width;
        height = width / aspect;
    }

    var cx = rect.x + rect.width / 2;
    var cy = rect.y + rect.height / 2;
    return {
        x: cx - width / 2,
        y: cy - height / 2,
        width: width,
        height: height,
    };
}

var mergePath = pathTool.mergePath;
/**
 * Resize a path to fit the rect
 * @param {module:zrender/graphic/Path} path
 * @param {Object} rect
 */

function resizePath(path, rect) {
    if (!path.applyTransform) {
        return;
    }

    var pathRect = path.getBoundingRect();
    var m = pathRect.calculateTransform(rect);
    path.applyTransform(m);
}
/**
 * Sub pixel optimize line for canvas
 *
 * @param {Object} param
 * @param {Object} [param.shape]
 * @param {number} [param.shape.x1]
 * @param {number} [param.shape.y1]
 * @param {number} [param.shape.x2]
 * @param {number} [param.shape.y2]
 * @param {Object} [param.style]
 * @param {number} [param.style.lineWidth]
 * @return {Object} Modified param
 */

function subPixelOptimizeLine(param) {
    subPixelOptimizeUtil.subPixelOptimizeLine(
        param.shape,
        param.shape,
        param.style
    );
    return param;
}
/**
 * Sub pixel optimize rect for canvas
 *
 * @param {Object} param
 * @param {Object} [param.shape]
 * @param {number} [param.shape.x]
 * @param {number} [param.shape.y]
 * @param {number} [param.shape.width]
 * @param {number} [param.shape.height]
 * @param {Object} [param.style]
 * @param {number} [param.style.lineWidth]
 * @return {Object} Modified param
 */

function subPixelOptimizeRect(param) {
    subPixelOptimizeUtil.subPixelOptimizeRect(
        param.shape,
        param.shape,
        param.style
    );
    return param;
}
/**
 * Sub pixel optimize for canvas
 *
 * @param {number} position Coordinate, such as x, y
 * @param {number} lineWidth Should be nonnegative integer.
 * @param {boolean=} positiveOrNegative Default false (negative).
 * @return {number} Optimized position.
 */

var subPixelOptimize = subPixelOptimizeUtil.subPixelOptimize;

function hasFillOrStroke(fillOrStroke) {
    return fillOrStroke != null && fillOrStroke !== "none";
} // Most lifted color are duplicated.

var liftedColorMap = zrUtil.createHashMap();
var liftedColorCount = 0;

function liftColor(color) {
    if (typeof color !== "string") {
        return color;
    }

    var liftedColor = liftedColorMap.get(color);

    if (!liftedColor) {
        liftedColor = colorTool.lift(color, -0.1);

        if (liftedColorCount < 10000) {
            liftedColorMap.set(color, liftedColor);
            liftedColorCount++;
        }
    }

    return liftedColor;
}

function cacheElementStl(el) {
    if (!el.__hoverStlDirty) {
        return;
    }

    el.__hoverStlDirty = false;
    var hoverStyle = el.__hoverStl;

    if (!hoverStyle) {
        el.__cachedNormalStl = el.__cachedNormalZ2 = null;
        return;
    }

    var normalStyle = (el.__cachedNormalStl = {});
    el.__cachedNormalZ2 = el.z2;
    var elStyle = el.style;

    for (var name in hoverStyle) {
        // See comment in `singleEnterEmphasis`.
        if (hoverStyle[name] != null) {
            normalStyle[name] = elStyle[name];
        }
    } // Always cache fill and stroke to normalStyle for lifting color.

    normalStyle.fill = elStyle.fill;
    normalStyle.stroke = elStyle.stroke;
}

function singleEnterEmphasis(el) {
    var hoverStl = el.__hoverStl;

    if (!hoverStl || el.__highlighted) {
        return;
    }

    var zr = el.__zr;
    var useHoverLayer = el.useHoverLayer && zr && zr.painter.type === "canvas";
    el.__highlighted = useHoverLayer ? "layer" : "plain";

    if (el.isGroup || (!zr && el.useHoverLayer)) {
        return;
    }

    var elTarget = el;
    var targetStyle = el.style;

    if (useHoverLayer) {
        elTarget = zr.addHover(el);
        targetStyle = elTarget.style;
    }

    rollbackDefaultTextStyle(targetStyle);

    if (!useHoverLayer) {
        cacheElementStl(elTarget);
    } // styles can be:
    // {
    //    label: {
    //        show: false,
    //        position: 'outside',
    //        fontSize: 18
    //    },
    //    emphasis: {
    //        label: {
    //            show: true
    //        }
    //    }
    // },
    // where properties of `emphasis` may not appear in `normal`. We previously use
    // module:echarts/util/model#defaultEmphasis to merge `normal` to `emphasis`.
    // But consider rich text and setOption in merge mode, it is impossible to cover
    // all properties in merge. So we use merge mode when setting style here.
    // But we choose the merge strategy that only properties that is not `null/undefined`.
    // Because when making a textStyle (espacially rich text), it is not easy to distinguish
    // `hasOwnProperty` and `null/undefined` in code, so we trade them as the same for simplicity.
    // But this strategy brings a trouble that `null/undefined` can not be used to remove
    // style any more in `emphasis`. Users can both set properties directly on normal and
    // emphasis to avoid this issue, or we might support `'none'` for this case if required.

    targetStyle.extendFrom(hoverStl);
    setDefaultHoverFillStroke(targetStyle, hoverStl, "fill");
    setDefaultHoverFillStroke(targetStyle, hoverStl, "stroke");
    applyDefaultTextStyle(targetStyle);

    if (!useHoverLayer) {
        el.dirty(false);
        el.z2 += Z2_EMPHASIS_LIFT;
    }
}

function setDefaultHoverFillStroke(targetStyle, hoverStyle, prop) {
    if (
        !hasFillOrStroke(hoverStyle[prop]) &&
        hasFillOrStroke(targetStyle[prop])
    ) {
        targetStyle[prop] = liftColor(targetStyle[prop]);
    }
}

function singleEnterNormal(el) {
    var highlighted = el.__highlighted;

    if (!highlighted) {
        return;
    }

    el.__highlighted = false;

    if (el.isGroup) {
        return;
    }

    if (highlighted === "layer") {
        el.__zr && el.__zr.removeHover(el);
    } else {
        var style = el.style;
        var normalStl = el.__cachedNormalStl;

        if (normalStl) {
            rollbackDefaultTextStyle(style);
            el.setStyle(normalStl);
            applyDefaultTextStyle(style);
        } // `__cachedNormalZ2` will not be reset if calling `setElementHoverStyle`
        // when `el` is on emphasis state. So here by comparing with 1, we try
        // hard to make the bug case rare.

        var normalZ2 = el.__cachedNormalZ2;

        if (normalZ2 != null && el.z2 - normalZ2 === Z2_EMPHASIS_LIFT) {
            el.z2 = normalZ2;
        }
    }
}

function traverseUpdate(el, updater, commonParam) {
    // If root is group, also enter updater for `highDownOnUpdate`.
    var fromState = NORMAL;
    var toState = NORMAL;
    var trigger; // See the rule of `highDownOnUpdate` on `graphic.setAsHighDownDispatcher`.

    el.__highlighted && ((fromState = EMPHASIS), (trigger = true));
    updater(el, commonParam);
    el.__highlighted && ((toState = EMPHASIS), (trigger = true));
    el.isGroup &&
        el.traverse(function (child) {
            !child.isGroup && updater(child, commonParam);
        });
    trigger &&
        el.__highDownOnUpdate &&
        el.__highDownOnUpdate(fromState, toState);
}
/**
 * Set hover style (namely "emphasis style") of element, based on the current
 * style of the given `el`.
 * This method should be called after all of the normal styles have been adopted
 * to the `el`. See the reason on `setHoverStyle`.
 *
 * @param {module:zrender/Element} el Should not be `zrender/container/Group`.
 * @param {Object} [el.hoverStyle] Can be set on el or its descendants,
 *        e.g., `el.hoverStyle = ...; graphic.setHoverStyle(el); `.
 *        Often used when item group has a label element and it's hoverStyle is different.
 * @param {Object|boolean} [hoverStl] The specified hover style.
 *        If set as `false`, disable the hover style.
 *        Similarly, The `el.hoverStyle` can alse be set
 *        as `false` to disable the hover style.
 *        Otherwise, use the default hover style if not provided.
 */

function setElementHoverStyle(el, hoverStl) {
    // For performance consideration, it might be better to make the "hover style" only the
    // difference properties from the "normal style", but not a entire copy of all styles.
    hoverStl = el.__hoverStl =
        hoverStl !== false && (el.hoverStyle || hoverStl || {});
    el.__hoverStlDirty = true; // FIXME
    // It is not completely right to save "normal"/"emphasis" flag on elements.
    // It probably should be saved on `data` of series. Consider the cases:
    // (1) A highlighted elements are moved out of the view port and re-enter
    // again by dataZoom.
    // (2) call `setOption` and replace elements totally when they are highlighted.

    if (el.__highlighted) {
        // Consider the case:
        // The styles of a highlighted `el` is being updated. The new "emphasis style"
        // should be adapted to the `el`. Notice here new "normal styles" should have
        // been set outside and the cached "normal style" is out of date.
        el.__cachedNormalStl = null; // Do not clear `__cachedNormalZ2` here, because setting `z2` is not a constraint
        // of this method. In most cases, `z2` is not set and hover style should be able
        // to rollback. Of course, that would bring bug, but only in a rare case, see
        // `doSingleLeaveHover` for details.

        singleEnterNormal(el);
        singleEnterEmphasis(el);
    }
}

function onElementMouseOver(e) {
    !shouldSilent(this, e) && // "emphasis" event highlight has higher priority than mouse highlight.
        !this.__highByOuter &&
        traverseUpdate(this, singleEnterEmphasis);
}

function onElementMouseOut(e) {
    !shouldSilent(this, e) && // "emphasis" event highlight has higher priority than mouse highlight.
        !this.__highByOuter &&
        traverseUpdate(this, singleEnterNormal);
}

function onElementEmphasisEvent(highlightDigit) {
    this.__highByOuter |= 1 << (highlightDigit || 0);
    traverseUpdate(this, singleEnterEmphasis);
}

function onElementNormalEvent(highlightDigit) {
    !(this.__highByOuter &= ~(1 << (highlightDigit || 0))) &&
        traverseUpdate(this, singleEnterNormal);
}

function shouldSilent(el, e) {
    return el.__highDownSilentOnTouch && e.zrByTouch;
}
/**
 * Set hover style (namely "emphasis style") of element,
 * based on the current style of the given `el`.
 *
 * (1)
 * **CONSTRAINTS** for this method:
 * <A> This method MUST be called after all of the normal styles having been adopted
 * to the `el`.
 * <B> The input `hoverStyle` (that is, "emphasis style") MUST be the subset of the
 * "normal style" having been set to the el.
 * <C> `color` MUST be one of the "normal styles" (because color might be lifted as
 * a default hover style).
 *
 * The reason: this method treat the current style of the `el` as the "normal style"
 * and cache them when enter/update the "emphasis style". Consider the case: the `el`
 * is in "emphasis" state and `setOption`/`dispatchAction` trigger the style updating
 * logic, where the el should shift from the original emphasis style to the new
 * "emphasis style" and should be able to "downplay" back to the new "normal style".
 *
 * Indeed, it is error-prone to make a interface has so many constraints, but I have
 * not found a better solution yet to fit the backward compatibility, performance and
 * the current programming style.
 *
 * (2)
 * Call the method for a "root" element once. Do not call it for each descendants.
 * If the descendants elemenets of a group has itself hover style different from the
 * root group, we can simply mount the style on `el.hoverStyle` for them, but should
 * not call this method for them.
 *
 * (3) These input parameters can be set directly on `el`:
 *
 * @param {module:zrender/Element} el
 * @param {Object} [el.hoverStyle] See `graphic.setElementHoverStyle`.
 * @param {boolean} [el.highDownSilentOnTouch=false] See `graphic.setAsHighDownDispatcher`.
 * @param {Function} [el.highDownOnUpdate] See `graphic.setAsHighDownDispatcher`.
 * @param {Object|boolean} [hoverStyle] See `graphic.setElementHoverStyle`.
 */

function setHoverStyle(el, hoverStyle) {
    setAsHighDownDispatcher(el, true);
    traverseUpdate(el, setElementHoverStyle, hoverStyle);
}
/**
 * @param {module:zrender/Element} el
 * @param {Function} [el.highDownOnUpdate] Called when state updated.
 *        Since `setHoverStyle` has the constraint that it must be called after
 *        all of the normal style updated, `highDownOnUpdate` is not needed to
 *        trigger if both `fromState` and `toState` is 'normal', and needed to
 *        trigger if both `fromState` and `toState` is 'emphasis', which enables
 *        to sync outside style settings to "emphasis" state.
 *        @this {string} This dispatcher `el`.
 *        @param {string} fromState Can be "normal" or "emphasis".
 *               `fromState` might equal to `toState`,
 *               for example, when this method is called when `el` is
 *               on "emphasis" state.
 *        @param {string} toState Can be "normal" or "emphasis".
 *
 *        FIXME
 *        CAUTION: Do not expose `highDownOnUpdate` outside echarts.
 *        Because it is not a complete solution. The update
 *        listener should not have been mount in element,
 *        and the normal/emphasis state should not have
 *        mantained on elements.
 *
 * @param {boolean} [el.highDownSilentOnTouch=false]
 *        In touch device, mouseover event will be trigger on touchstart event
 *        (see module:zrender/dom/HandlerProxy). By this mechanism, we can
 *        conveniently use hoverStyle when tap on touch screen without additional
 *        code for compatibility.
 *        But if the chart/component has select feature, which usually also use
 *        hoverStyle, there might be conflict between 'select-highlight' and
 *        'hover-highlight' especially when roam is enabled (see geo for example).
 *        In this case, `highDownSilentOnTouch` should be used to disable
 *        hover-highlight on touch device.
 * @param {boolean} [asDispatcher=true] If `false`, do not set as "highDownDispatcher".
 */

function setAsHighDownDispatcher(el, asDispatcher) {
    var disable = asDispatcher === false; // Make `highDownSilentOnTouch` and `highDownOnUpdate` only work after
    // `setAsHighDownDispatcher` called. Avoid it is modified by user unexpectedly.

    el.__highDownSilentOnTouch = el.highDownSilentOnTouch;
    el.__highDownOnUpdate = el.highDownOnUpdate; // Simple optimize, since this method might be
    // called for each elements of a group in some cases.

    if (!disable || el.__highDownDispatcher) {
        var method = disable ? "off" : "on"; // Duplicated function will be auto-ignored, see Eventful.js.

        el[method]("mouseover", onElementMouseOver)[method](
            "mouseout",
            onElementMouseOut
        ); // Emphasis, normal can be triggered manually by API or other components like hover link.

        el[method]("emphasis", onElementEmphasisEvent)[method](
            "normal",
            onElementNormalEvent
        ); // Also keep previous record.

        el.__highByOuter = el.__highByOuter || 0;
        el.__highDownDispatcher = !disable;
    }
}
/**
 * @param {module:zrender/src/Element} el
 * @return {boolean}
 */

function isHighDownDispatcher(el) {
    return !!(el && el.__highDownDispatcher);
}
/**
 * Support hightlight/downplay record on each elements.
 * For the case: hover highlight/downplay (legend, visualMap, ...) and
 * user triggerred hightlight/downplay should not conflict.
 * Only all of the highlightDigit cleared, return to normal.
 * @param {string} highlightKey
 * @return {number} highlightDigit
 */

function getHighlightDigit(highlightKey) {
    var highlightDigit = _highlightKeyMap[highlightKey];

    if (highlightDigit == null && _highlightNextDigit <= 32) {
        highlightDigit = _highlightKeyMap[highlightKey] = _highlightNextDigit++;
    }

    return highlightDigit;
}
/**
 * See more info in `setTextStyleCommon`.
 * @param {Object|module:zrender/graphic/Style} normalStyle
 * @param {Object} emphasisStyle
 * @param {module:echarts/model/Model} normalModel
 * @param {module:echarts/model/Model} emphasisModel
 * @param {Object} opt Check `opt` of `setTextStyleCommon` to find other props.
 * @param {string|Function} [opt.defaultText]
 * @param {module:echarts/model/Model} [opt.labelFetcher] Fetch text by
 *      `opt.labelFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)`
 * @param {module:echarts/model/Model} [opt.labelDataIndex] Fetch text by
 *      `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)`
 * @param {module:echarts/model/Model} [opt.labelDimIndex] Fetch text by
 *      `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)`
 * @param {Object} [normalSpecified]
 * @param {Object} [emphasisSpecified]
 */

function setLabelStyle(
    normalStyle,
    emphasisStyle,
    normalModel,
    emphasisModel,
    opt,
    normalSpecified,
    emphasisSpecified
) {
    opt = opt || EMPTY_OBJ;
    var labelFetcher = opt.labelFetcher;
    var labelDataIndex = opt.labelDataIndex;
    var labelDimIndex = opt.labelDimIndex; // This scenario, `label.normal.show = true; label.emphasis.show = false`,
    // is not supported util someone requests.

    var showNormal = normalModel.getShallow("show");
    var showEmphasis = emphasisModel.getShallow("show"); // Consider performance, only fetch label when necessary.
    // If `normal.show` is `false` and `emphasis.show` is `true` and `emphasis.formatter` is not set,
    // label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`.

    var baseText;

    if (showNormal || showEmphasis) {
        if (labelFetcher) {
            baseText = labelFetcher.getFormattedLabel(
                labelDataIndex,
                "normal",
                null,
                labelDimIndex
            );
        }

        if (baseText == null) {
            baseText = zrUtil.isFunction(opt.defaultText)
                ? opt.defaultText(labelDataIndex, opt)
                : opt.defaultText;
        }
    }

    var normalStyleText = showNormal ? baseText : null;
    var emphasisStyleText = showEmphasis
        ? zrUtil.retrieve2(
              labelFetcher
                  ? labelFetcher.getFormattedLabel(
                        labelDataIndex,
                        "emphasis",
                        null,
                        labelDimIndex
                    )
                  : null,
              baseText
          )
        : null; // Optimize: If style.text is null, text will not be drawn.

    if (normalStyleText != null || emphasisStyleText != null) {
        // Always set `textStyle` even if `normalStyle.text` is null, because default
        // values have to be set on `normalStyle`.
        // If we set default values on `emphasisStyle`, consider case:
        // Firstly, `setOption(... label: {normal: {text: null}, emphasis: {show: true}} ...);`
        // Secondly, `setOption(... label: {noraml: {show: true, text: 'abc', color: 'red'} ...);`
        // Then the 'red' will not work on emphasis.
        setTextStyle(normalStyle, normalModel, normalSpecified, opt);
        setTextStyle(
            emphasisStyle,
            emphasisModel,
            emphasisSpecified,
            opt,
            true
        );
    }

    normalStyle.text = normalStyleText;
    emphasisStyle.text = emphasisStyleText;
}
/**
 * Modify label style manually.
 * Only works after `setLabelStyle` and `setElementHoverStyle` called.
 *
 * @param {module:zrender/src/Element} el
 * @param {Object} [normalStyleProps] optional
 * @param {Object} [emphasisStyleProps] optional
 */

function modifyLabelStyle(el, normalStyleProps, emphasisStyleProps) {
    var elStyle = el.style;

    if (normalStyleProps) {
        rollbackDefaultTextStyle(elStyle);
        el.setStyle(normalStyleProps);
        applyDefaultTextStyle(elStyle);
    }

    elStyle = el.__hoverStl;

    if (emphasisStyleProps && elStyle) {
        rollbackDefaultTextStyle(elStyle);
        zrUtil.extend(elStyle, emphasisStyleProps);
        applyDefaultTextStyle(elStyle);
    }
}
/**
 * Set basic textStyle properties.
 * See more info in `setTextStyleCommon`.
 * @param {Object|module:zrender/graphic/Style} textStyle
 * @param {module:echarts/model/Model} model
 * @param {Object} [specifiedTextStyle] Can be overrided by settings in model.
 * @param {Object} [opt] See `opt` of `setTextStyleCommon`.
 * @param {boolean} [isEmphasis]
 */

function setTextStyle(
    textStyle,
    textStyleModel,
    specifiedTextStyle,
    opt,
    isEmphasis
) {
    setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis);
    specifiedTextStyle && zrUtil.extend(textStyle, specifiedTextStyle); // textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false);

    return textStyle;
}
/**
 * Set text option in the style.
 * See more info in `setTextStyleCommon`.
 * @deprecated
 * @param {Object} textStyle
 * @param {module:echarts/model/Model} labelModel
 * @param {string|boolean} defaultColor Default text color.
 *        If set as false, it will be processed as a emphasis style.
 */

function setText(textStyle, labelModel, defaultColor) {
    var opt = {
        isRectText: true,
    };
    var isEmphasis;

    if (defaultColor === false) {
        isEmphasis = true;
    } else {
        // Support setting color as 'auto' to get visual color.
        opt.autoColor = defaultColor;
    }

    setTextStyleCommon(textStyle, labelModel, opt, isEmphasis); // textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false);
}
/**
 * The uniform entry of set text style, that is, retrieve style definitions
 * from `model` and set to `textStyle` object.
 *
 * Never in merge mode, but in overwrite mode, that is, all of the text style
 * properties will be set. (Consider the states of normal and emphasis and
 * default value can be adopted, merge would make the logic too complicated
 * to manage.)
 *
 * The `textStyle` object can either be a plain object or an instance of
 * `zrender/src/graphic/Style`, and either be the style of normal or emphasis.
 * After this mothod called, the `textStyle` object can then be used in
 * `el.setStyle(textStyle)` or `el.hoverStyle = textStyle`.
 *
 * Default value will be adopted and `insideRollbackOpt` will be created.
 * See `applyDefaultTextStyle` `rollbackDefaultTextStyle` for more details.
 *
 * opt: {
 *      disableBox: boolean, Whether diable drawing box of block (outer most).
 *      isRectText: boolean,
 *      autoColor: string, specify a color when color is 'auto',
 *              for textFill, textStroke, textBackgroundColor, and textBorderColor.
 *              If autoColor specified, it is used as default textFill.
 *      useInsideStyle:
 *              `true`: Use inside style (textFill, textStroke, textStrokeWidth)
 *                  if `textFill` is not specified.
 *              `false`: Do not use inside style.
 *              `null/undefined`: use inside style if `isRectText` is true and
 *                  `textFill` is not specified and textPosition contains `'inside'`.
 *      forceRich: boolean
 * }
 */

function setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis) {
    // Consider there will be abnormal when merge hover style to normal style if given default value.
    opt = opt || EMPTY_OBJ;

    if (opt.isRectText) {
        var textPosition;

        if (opt.getTextPosition) {
            textPosition = opt.getTextPosition(textStyleModel, isEmphasis);
        } else {
            textPosition =
                textStyleModel.getShallow("position") ||
                (isEmphasis ? null : "inside"); // 'outside' is not a valid zr textPostion value, but used
            // in bar series, and magric type should be considered.

            textPosition === "outside" && (textPosition = "top");
        }

        textStyle.textPosition = textPosition;
        textStyle.textOffset = textStyleModel.getShallow("offset");
        var labelRotate = textStyleModel.getShallow("rotate");
        labelRotate != null && (labelRotate *= Math.PI / 180);
        textStyle.textRotation = labelRotate;
        textStyle.textDistance = zrUtil.retrieve2(
            textStyleModel.getShallow("distance"),
            isEmphasis ? null : 5
        );
    }

    var ecModel = textStyleModel.ecModel;
    var globalTextStyle = ecModel && ecModel.option.textStyle; // Consider case:
    // {
    //     data: [{
    //         value: 12,
    //         label: {
    //             rich: {
    //                 // no 'a' here but using parent 'a'.
    //             }
    //         }
    //     }],
    //     rich: {
    //         a: { ... }
    //     }
    // }

    var richItemNames = getRichItemNames(textStyleModel);
    var richResult;

    if (richItemNames) {
        richResult = {};

        for (var name in richItemNames) {
            if (richItemNames.hasOwnProperty(name)) {
                // Cascade is supported in rich.
                var richTextStyle = textStyleModel.getModel(["rich", name]); // In rich, never `disableBox`.
                // FIXME: consider `label: {formatter: '{a|xx}', color: 'blue', rich: {a: {}}}`,
                // the default color `'blue'` will not be adopted if no color declared in `rich`.
                // That might confuses users. So probably we should put `textStyleModel` as the
                // root ancestor of the `richTextStyle`. But that would be a break change.

                setTokenTextStyle(
                    (richResult[name] = {}),
                    richTextStyle,
                    globalTextStyle,
                    opt,
                    isEmphasis
                );
            }
        }
    }

    textStyle.rich = richResult;
    setTokenTextStyle(
        textStyle,
        textStyleModel,
        globalTextStyle,
        opt,
        isEmphasis,
        true
    );

    if (opt.forceRich && !opt.textStyle) {
        opt.textStyle = {};
    }

    return textStyle;
} // Consider case:
// {
//     data: [{
//         value: 12,
//         label: {
//             rich: {
//                 // no 'a' here but using parent 'a'.
//             }
//         }
//     }],
//     rich: {
//         a: { ... }
//     }
// }

function getRichItemNames(textStyleModel) {
    // Use object to remove duplicated names.
    var richItemNameMap;

    while (textStyleModel && textStyleModel !== textStyleModel.ecModel) {
        var rich = (textStyleModel.option || EMPTY_OBJ).rich;

        if (rich) {
            richItemNameMap = richItemNameMap || {};

            for (var name in rich) {
                if (rich.hasOwnProperty(name)) {
                    richItemNameMap[name] = 1;
                }
            }
        }

        textStyleModel = textStyleModel.parentModel;
    }

    return richItemNameMap;
}

function setTokenTextStyle(
    textStyle,
    textStyleModel,
    globalTextStyle,
    opt,
    isEmphasis,
    isBlock
) {
    // In merge mode, default value should not be given.
    globalTextStyle = (!isEmphasis && globalTextStyle) || EMPTY_OBJ;
    textStyle.textFill =
        getAutoColor(textStyleModel.getShallow("color"), opt) ||
        globalTextStyle.color;
    textStyle.textStroke =
        getAutoColor(textStyleModel.getShallow("textBorderColor"), opt) ||
        globalTextStyle.textBorderColor;
    textStyle.textStrokeWidth = zrUtil.retrieve2(
        textStyleModel.getShallow("textBorderWidth"),
        globalTextStyle.textBorderWidth
    );

    if (!isEmphasis) {
        if (isBlock) {
            textStyle.insideRollbackOpt = opt;
            applyDefaultTextStyle(textStyle);
        } // Set default finally.

        if (textStyle.textFill == null) {
            textStyle.textFill = opt.autoColor;
        }
    } // Do not use `getFont` here, because merge should be supported, where
    // part of these properties may be changed in emphasis style, and the
    // others should remain their original value got from normal style.

    textStyle.fontStyle =
        textStyleModel.getShallow("fontStyle") || globalTextStyle.fontStyle;
    textStyle.fontWeight =
        textStyleModel.getShallow("fontWeight") || globalTextStyle.fontWeight;
    textStyle.fontSize =
        textStyleModel.getShallow("fontSize") || globalTextStyle.fontSize;
    textStyle.fontFamily =
        textStyleModel.getShallow("fontFamily") || globalTextStyle.fontFamily;
    textStyle.textAlign = textStyleModel.getShallow("align");
    textStyle.textVerticalAlign =
        textStyleModel.getShallow("verticalAlign") ||
        textStyleModel.getShallow("baseline");
    textStyle.textLineHeight = textStyleModel.getShallow("lineHeight");
    textStyle.textWidth = textStyleModel.getShallow("width");
    textStyle.textHeight = textStyleModel.getShallow("height");
    textStyle.textTag = textStyleModel.getShallow("tag");

    if (!isBlock || !opt.disableBox) {
        textStyle.textBackgroundColor = getAutoColor(
            textStyleModel.getShallow("backgroundColor"),
            opt
        );
        textStyle.textPadding = textStyleModel.getShallow("padding");
        textStyle.textBorderColor = getAutoColor(
            textStyleModel.getShallow("borderColor"),
            opt
        );
        textStyle.textBorderWidth = textStyleModel.getShallow("borderWidth");
        textStyle.textBorderRadius = textStyleModel.getShallow("borderRadius");
        textStyle.textBoxShadowColor = textStyleModel.getShallow("shadowColor");
        textStyle.textBoxShadowBlur = textStyleModel.getShallow("shadowBlur");
        textStyle.textBoxShadowOffsetX =
            textStyleModel.getShallow("shadowOffsetX");
        textStyle.textBoxShadowOffsetY =
            textStyleModel.getShallow("shadowOffsetY");
    }

    textStyle.textShadowColor =
        textStyleModel.getShallow("textShadowColor") ||
        globalTextStyle.textShadowColor;
    textStyle.textShadowBlur =
        textStyleModel.getShallow("textShadowBlur") ||
        globalTextStyle.textShadowBlur;
    textStyle.textShadowOffsetX =
        textStyleModel.getShallow("textShadowOffsetX") ||
        globalTextStyle.textShadowOffsetX;
    textStyle.textShadowOffsetY =
        textStyleModel.getShallow("textShadowOffsetY") ||
        globalTextStyle.textShadowOffsetY;
}

function getAutoColor(color, opt) {
    return color !== "auto"
        ? color
        : opt && opt.autoColor
        ? opt.autoColor
        : null;
}
/**
 * Give some default value to the input `textStyle` object, based on the current settings
 * in this `textStyle` object.
 *
 * The Scenario:
 * when text position is `inside` and `textFill` is not specified, we show
 * text border by default for better view. But it should be considered that text position
 * might be changed when hovering or being emphasis, where the `insideRollback` is used to
 * restore the style.
 *
 * Usage (& NOTICE):
 * When a style object (eithor plain object or instance of `zrender/src/graphic/Style`) is
 * about to be modified on its text related properties, `rollbackDefaultTextStyle` should
 * be called before the modification and `applyDefaultTextStyle` should be called after that.
 * (For the case that all of the text related properties is reset, like `setTextStyleCommon`
 * does, `rollbackDefaultTextStyle` is not needed to be called).
 */

function applyDefaultTextStyle(textStyle) {
    var textPosition = textStyle.textPosition;
    var opt = textStyle.insideRollbackOpt;
    var insideRollback;

    if (opt && textStyle.textFill == null) {
        var autoColor = opt.autoColor;
        var isRectText = opt.isRectText;
        var useInsideStyle = opt.useInsideStyle;
        var useInsideStyleCache =
            useInsideStyle !== false &&
            (useInsideStyle === true ||
                (isRectText &&
                    textPosition && // textPosition can be [10, 30]
                    typeof textPosition === "string" &&
                    textPosition.indexOf("inside") >= 0));
        var useAutoColorCache = !useInsideStyleCache && autoColor != null; // All of the props declared in `CACHED_LABEL_STYLE_PROPERTIES` are to be cached.

        if (useInsideStyleCache || useAutoColorCache) {
            insideRollback = {
                textFill: textStyle.textFill,
                textStroke: textStyle.textStroke,
                textStrokeWidth: textStyle.textStrokeWidth,
            };
        }

        if (useInsideStyleCache) {
            textStyle.textFill = "#fff"; // Consider text with #fff overflow its container.

            if (textStyle.textStroke == null) {
                textStyle.textStroke = autoColor;
                textStyle.textStrokeWidth == null &&
                    (textStyle.textStrokeWidth = 2);
            }
        }

        if (useAutoColorCache) {
            textStyle.textFill = autoColor;
        }
    } // Always set `insideRollback`, so that the previous one can be cleared.

    textStyle.insideRollback = insideRollback;
}
/**
 * Consider the case: in a scatter,
 * label: {
 *     normal: {position: 'inside'},
 *     emphasis: {position: 'top'}
 * }
 * In the normal state, the `textFill` will be set as '#fff' for pretty view (see
 * `applyDefaultTextStyle`), but when switching to emphasis state, the `textFill`
 * should be retured to 'autoColor', but not keep '#fff'.
 */

function rollbackDefaultTextStyle(style) {
    var insideRollback = style.insideRollback;

    if (insideRollback) {
        // Reset all of the props in `CACHED_LABEL_STYLE_PROPERTIES`.
        style.textFill = insideRollback.textFill;
        style.textStroke = insideRollback.textStroke;
        style.textStrokeWidth = insideRollback.textStrokeWidth;
        style.insideRollback = null;
    }
}

function getFont(opt, ecModel) {
    var gTextStyleModel = ecModel && ecModel.getModel("textStyle");
    return zrUtil.trim(
        [
            // FIXME in node-canvas fontWeight is before fontStyle
            opt.fontStyle ||
                (gTextStyleModel && gTextStyleModel.getShallow("fontStyle")) ||
                "",
            opt.fontWeight ||
                (gTextStyleModel && gTextStyleModel.getShallow("fontWeight")) ||
                "",
            (opt.fontSize ||
                (gTextStyleModel && gTextStyleModel.getShallow("fontSize")) ||
                12) + "px",
            opt.fontFamily ||
                (gTextStyleModel && gTextStyleModel.getShallow("fontFamily")) ||
                "sans-serif",
        ].join(" ")
    );
}

function animateOrSetProps(
    isUpdate,
    el,
    props,
    animatableModel,
    dataIndex,
    cb
) {
    if (typeof dataIndex === "function") {
        cb = dataIndex;
        dataIndex = null;
    } // Do not check 'animation' property directly here. Consider this case:
    // animation model is an `itemModel`, whose does not have `isAnimationEnabled`
    // but its parent model (`seriesModel`) does.

    var animationEnabled =
        animatableModel && animatableModel.isAnimationEnabled();

    if (animationEnabled) {
        var postfix = isUpdate ? "Update" : "";
        var duration = animatableModel.getShallow(
            "animationDuration" + postfix
        );
        var animationEasing = animatableModel.getShallow(
            "animationEasing" + postfix
        );
        var animationDelay = animatableModel.getShallow(
            "animationDelay" + postfix
        );

        if (typeof animationDelay === "function") {
            animationDelay = animationDelay(
                dataIndex,
                animatableModel.getAnimationDelayParams
                    ? animatableModel.getAnimationDelayParams(el, dataIndex)
                    : null
            );
        }

        if (typeof duration === "function") {
            duration = duration(dataIndex);
        }

        duration > 0
            ? el.animateTo(
                  props,
                  duration,
                  animationDelay || 0,
                  animationEasing,
                  cb,
                  !!cb
              )
            : (el.stopAnimation(), el.attr(props), cb && cb());
    } else {
        el.stopAnimation();
        el.attr(props);
        cb && cb();
    }
}
/**
 * Update graphic element properties with or without animation according to the
 * configuration in series.
 *
 * Caution: this method will stop previous animation.
 * So do not use this method to one element twice before
 * animation starts, unless you know what you are doing.
 *
 * @param {module:zrender/Element} el
 * @param {Object} props
 * @param {module:echarts/model/Model} [animatableModel]
 * @param {number} [dataIndex]
 * @param {Function} [cb]
 * @example
 *     graphic.updateProps(el, {
 *         position: [100, 100]
 *     }, seriesModel, dataIndex, function () { console.log('Animation done!'); });
 *     // Or
 *     graphic.updateProps(el, {
 *         position: [100, 100]
 *     }, seriesModel, function () { console.log('Animation done!'); });
 */

function updateProps(el, props, animatableModel, dataIndex, cb) {
    animateOrSetProps(true, el, props, animatableModel, dataIndex, cb);
}
/**
 * Init graphic element properties with or without animation according to the
 * configuration in series.
 *
 * Caution: this method will stop previous animation.
 * So do not use this method to one element twice before
 * animation starts, unless you know what you are doing.
 *
 * @param {module:zrender/Element} el
 * @param {Object} props
 * @param {module:echarts/model/Model} [animatableModel]
 * @param {number} [dataIndex]
 * @param {Function} cb
 */

function initProps(el, props, animatableModel, dataIndex, cb) {
    animateOrSetProps(false, el, props, animatableModel, dataIndex, cb);
}
/**
 * Get transform matrix of target (param target),
 * in coordinate of its ancestor (param ancestor)
 *
 * @param {module:zrender/mixin/Transformable} target
 * @param {module:zrender/mixin/Transformable} [ancestor]
 */

function getTransform(target, ancestor) {
    var mat = matrix.identity([]);

    while (target && target !== ancestor) {
        matrix.mul(mat, target.getLocalTransform(), mat);
        target = target.parent;
    }

    return mat;
}
/**
 * Apply transform to an vertex.
 * @param {Array.<number>} target [x, y]
 * @param {Array.<number>|TypedArray.<number>|Object} transform Can be:
 *      + Transform matrix: like [1, 0, 0, 1, 0, 0]
 *      + {position, rotation, scale}, the same as `zrender/Transformable`.
 * @param {boolean=} invert Whether use invert matrix.
 * @return {Array.<number>} [x, y]
 */

function applyTransform(target, transform, invert) {
    if (transform && !zrUtil.isArrayLike(transform)) {
        transform = Transformable.getLocalTransform(transform);
    }

    if (invert) {
        transform = matrix.invert([], transform);
    }

    return vector.applyTransform([], target, transform);
}
/**
 * @param {string} direction 'left' 'right' 'top' 'bottom'
 * @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0]
 * @param {boolean=} invert Whether use invert matrix.
 * @return {string} Transformed direction. 'left' 'right' 'top' 'bottom'
 */

function transformDirection(direction, transform, invert) {
    // Pick a base, ensure that transform result will not be (0, 0).
    var hBase =
        transform[4] === 0 || transform[5] === 0 || transform[0] === 0
            ? 1
            : Math.abs((2 * transform[4]) / transform[0]);
    var vBase =
        transform[4] === 0 || transform[5] === 0 || transform[2] === 0
            ? 1
            : Math.abs((2 * transform[4]) / transform[2]);
    var vertex = [
        direction === "left" ? -hBase : direction === "right" ? hBase : 0,
        direction === "top" ? -vBase : direction === "bottom" ? vBase : 0,
    ];
    vertex = applyTransform(vertex, transform, invert);
    return Math.abs(vertex[0]) > Math.abs(vertex[1])
        ? vertex[0] > 0
            ? "right"
            : "left"
        : vertex[1] > 0
        ? "bottom"
        : "top";
}
/**
 * Apply group transition animation from g1 to g2.
 * If no animatableModel, no animation.
 */

function groupTransition(g1, g2, animatableModel, cb) {
    if (!g1 || !g2) {
        return;
    }

    function getElMap(g) {
        var elMap = {};
        g.traverse(function (el) {
            if (!el.isGroup && el.anid) {
                elMap[el.anid] = el;
            }
        });
        return elMap;
    }

    function getAnimatableProps(el) {
        var obj = {
            position: vector.clone(el.position),
            rotation: el.rotation,
        };

        if (el.shape) {
            obj.shape = zrUtil.extend({}, el.shape);
        }

        return obj;
    }

    var elMap1 = getElMap(g1);
    g2.traverse(function (el) {
        if (!el.isGroup && el.anid) {
            var oldEl = elMap1[el.anid];

            if (oldEl) {
                var newProp = getAnimatableProps(el);
                el.attr(getAnimatableProps(oldEl));
                updateProps(el, newProp, animatableModel, el.dataIndex);
            } // else {
            //     if (el.previousProps) {
            //         graphic.updateProps
            //     }
            // }
        }
    });
}
/**
 * @param {Array.<Array.<number>>} points Like: [[23, 44], [53, 66], ...]
 * @param {Object} rect {x, y, width, height}
 * @return {Array.<Array.<number>>} A new clipped points.
 */

function clipPointsByRect(points, rect) {
    // FIXME: this way migth be incorrect when grpahic clipped by a corner.
    // and when element have border.
    return zrUtil.map(points, function (point) {
        var x = point[0];
        x = mathMax(x, rect.x);
        x = mathMin(x, rect.x + rect.width);
        var y = point[1];
        y = mathMax(y, rect.y);
        y = mathMin(y, rect.y + rect.height);
        return [x, y];
    });
}
/**
 * @param {Object} targetRect {x, y, width, height}
 * @param {Object} rect {x, y, width, height}
 * @return {Object} A new clipped rect. If rect size are negative, return undefined.
 */

function clipRectByRect(targetRect, rect) {
    var x = mathMax(targetRect.x, rect.x);
    var x2 = mathMin(targetRect.x + targetRect.width, rect.x + rect.width);
    var y = mathMax(targetRect.y, rect.y);
    var y2 = mathMin(targetRect.y + targetRect.height, rect.y + rect.height); // If the total rect is cliped, nothing, including the border,
    // should be painted. So return undefined.

    if (x2 >= x && y2 >= y) {
        return {
            x: x,
            y: y,
            width: x2 - x,
            height: y2 - y,
        };
    }
}
/**
 * @param {string} iconStr Support 'image://' or 'path://' or direct svg path.
 * @param {Object} [opt] Properties of `module:zrender/Element`, except `style`.
 * @param {Object} [rect] {x, y, width, height}
 * @return {module:zrender/Element} Icon path or image element.
 */

function createIcon(iconStr, opt, rect) {
    opt = zrUtil.extend(
        {
            rectHover: true,
        },
        opt
    );
    var style = (opt.style = {
        strokeNoScale: true,
    });
    rect = rect || {
        x: -1,
        y: -1,
        width: 2,
        height: 2,
    };

    if (iconStr) {
        return iconStr.indexOf("image://") === 0
            ? ((style.image = iconStr.slice(8)),
              zrUtil.defaults(style, rect),
              new ZImage(opt))
            : makePath(iconStr.replace("path://", ""), opt, rect, "center");
    }
}
/**
 * Return `true` if the given line (line `a`) and the given polygon
 * are intersect.
 * Note that we do not count colinear as intersect here because no
 * requirement for that. We could do that if required in future.
 *
 * @param {number} a1x
 * @param {number} a1y
 * @param {number} a2x
 * @param {number} a2y
 * @param {Array.<Array.<number>>} points Points of the polygon.
 * @return {boolean}
 */

function linePolygonIntersect(a1x, a1y, a2x, a2y, points) {
    for (var i = 0, p2 = points[points.length - 1]; i < points.length; i++) {
        var p = points[i];

        if (lineLineIntersect(a1x, a1y, a2x, a2y, p[0], p[1], p2[0], p2[1])) {
            return true;
        }

        p2 = p;
    }
}
/**
 * Return `true` if the given two lines (line `a` and line `b`)
 * are intersect.
 * Note that we do not count colinear as intersect here because no
 * requirement for that. We could do that if required in future.
 *
 * @param {number} a1x
 * @param {number} a1y
 * @param {number} a2x
 * @param {number} a2y
 * @param {number} b1x
 * @param {number} b1y
 * @param {number} b2x
 * @param {number} b2y
 * @return {boolean}
 */

function lineLineIntersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y) {
    // let `vec_m` to be `vec_a2 - vec_a1` and `vec_n` to be `vec_b2 - vec_b1`.
    var mx = a2x - a1x;
    var my = a2y - a1y;
    var nx = b2x - b1x;
    var ny = b2y - b1y; // `vec_m` and `vec_n` are parallel iff
    //     exising `k` such that `vec_m = k · vec_n`, equivalent to `vec_m X vec_n = 0`.

    var nmCrossProduct = crossProduct2d(nx, ny, mx, my);

    if (nearZero(nmCrossProduct)) {
        return false;
    } // `vec_m` and `vec_n` are intersect iff
    //     existing `p` and `q` in [0, 1] such that `vec_a1 + p * vec_m = vec_b1 + q * vec_n`,
    //     such that `q = ((vec_a1 - vec_b1) X vec_m) / (vec_n X vec_m)`
    //           and `p = ((vec_a1 - vec_b1) X vec_n) / (vec_n X vec_m)`.

    var b1a1x = a1x - b1x;
    var b1a1y = a1y - b1y;
    var q = crossProduct2d(b1a1x, b1a1y, mx, my) / nmCrossProduct;

    if (q < 0 || q > 1) {
        return false;
    }

    var p = crossProduct2d(b1a1x, b1a1y, nx, ny) / nmCrossProduct;

    if (p < 0 || p > 1) {
        return false;
    }

    return true;
}
/**
 * Cross product of 2-dimension vector.
 */

function crossProduct2d(x1, y1, x2, y2) {
    return x1 * y2 - x2 * y1;
}

function nearZero(val) {
    return val <= 1e-6 && val >= -1e-6;
} // Register built-in shapes. These shapes might be overwirtten
// by users, although we do not recommend that.

registerShape("circle", Circle);
registerShape("sector", Sector);
registerShape("ring", Ring);
registerShape("polygon", Polygon);
registerShape("polyline", Polyline);
registerShape("rect", Rect);
registerShape("line", Line);
registerShape("bezierCurve", BezierCurve);
registerShape("arc", Arc);
exports.Z2_EMPHASIS_LIFT = Z2_EMPHASIS_LIFT;
exports.CACHED_LABEL_STYLE_PROPERTIES = CACHED_LABEL_STYLE_PROPERTIES;
exports.extendShape = extendShape;
exports.extendPath = extendPath;
exports.registerShape = registerShape;
exports.getShapeClass = getShapeClass;
exports.makePath = makePath;
exports.makeImage = makeImage;
exports.mergePath = mergePath;
exports.resizePath = resizePath;
exports.subPixelOptimizeLine = subPixelOptimizeLine;
exports.subPixelOptimizeRect = subPixelOptimizeRect;
exports.subPixelOptimize = subPixelOptimize;
exports.setElementHoverStyle = setElementHoverStyle;
exports.setHoverStyle = setHoverStyle;
exports.setAsHighDownDispatcher = setAsHighDownDispatcher;
exports.isHighDownDispatcher = isHighDownDispatcher;
exports.getHighlightDigit = getHighlightDigit;
exports.setLabelStyle = setLabelStyle;
exports.modifyLabelStyle = modifyLabelStyle;
exports.setTextStyle = setTextStyle;
exports.setText = setText;
exports.getFont = getFont;
exports.updateProps = updateProps;
exports.initProps = initProps;
exports.getTransform = getTransform;
exports.applyTransform = applyTransform;
exports.transformDirection = transformDirection;
exports.groupTransition = groupTransition;
exports.clipPointsByRect = clipPointsByRect;
exports.clipRectByRect = clipRectByRect;
exports.createIcon = createIcon;
exports.linePolygonIntersect = linePolygonIntersect;
exports.lineLineIntersect = lineLineIntersect;
